package com.yao.ui.psd;

import android.annotation.TargetApi;
import android.content.DialogInterface;
import android.hardware.biometrics.BiometricPrompt;
import android.os.Build;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.support.annotation.Nullable;
import android.util.Base64;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
import com.base.fragment.BaseDialogFragment;
import com.base.helper.ActivityHelper;
import com.yao.R;
import com.yao.helper.DialogFragmentHelper;
import com.yao.ui.LoginActivity;
import com.yao.util.AlertUtil;
import com.yao.util.FingerprintUtil;
import com.yao.util.PreferenceCache;
import com.yao.widget.PsdGestureView;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import java.security.*;
import java.security.spec.ECGenParameterSpec;
import java.util.List;

public class PsdGestureFragment extends BaseDialogFragment implements PsdGestureView.GestureCallBack, DialogInterface.OnKeyListener, FingerprintDialogFragment.OnDialogResultListener {

    private PsdGestureView pgv;
    private TextView tvReset;
    private static final String DEFAULT_KEY_NAME = "yao_key";
    private CancellationSignal mCancellationSignal;
    private KeyStore keyStore;
    private Cipher cipher;

    public static PsdGestureFragment newInstance() {
        PsdGestureFragment fragment = new PsdGestureFragment();
        return fragment;
    }

    @Override
    protected int getLayoutId() {
        return R.layout.activity_psd_gesture;
    }

    @Override
    protected void release() {
        pgv.setGestureCallBack(null);
        pgv.release();
    }

    @Override
    protected void initView(Bundle bundle) {
        pgv = findView(R.id.pgv);
        pgv.setGestureCallBack(this);
        getDialog().setOnKeyListener(this);

        tvReset = findView(R.id.tv_reset);


        if (pgv.getState() == PsdGestureView.STATE_REGISTER) {
            tvReset.setVisibility(View.GONE);
        } else {
            tvReset.setVisibility(View.VISIBLE);
        }

        tvReset.setOnClickListener(v -> {
            pgv.clearCache();
            PreferenceCache.putToken("");
            PreferenceCache.putAutoLogin(false);
            LoginActivity.startActivity(getContext());
            if (getActivity() != null) {
                getActivity().finish();
            }
        });

        if (pgv.loadSharedPrefferenceData().size() > 0) {
            if (FingerprintUtil.isSupportFingerprint(getContext())) {
                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
                    initKey();
                    cipher = initCipher();
                    FingerprintDialogFragment fragment = FingerprintDialogFragment.newInstance();
                    fragment.setCipher(cipher);
                    fragment.setOnDialogResultListener(this);
                    DialogFragmentHelper.showDialogFragment(getChildFragmentManager(), fragment, "Fingerprint");
                } else {
                    initBiometricP();
                }
            }
        }
    }

    @Override
    protected boolean isMatchParent() {
        return true;
    }

    @Override
    public void gestureVerifySuccessListener(int stateFlag, List<PsdGestureView.GestureBean> data, boolean success) {
        if (success) {
            dismissAllowingStateLoss();
        }
    }

    @Override
    public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK) {
            ActivityHelper.getInstance().finishAllActivity();
            return true;
        } else {
            //这里注意当不是返回键时需将事件扩散，否则无法处理其他点击事件
            return false;
        }
    }

    /**
     * Android P+ 指纹
     */
    @TargetApi(Build.VERSION_CODES.P)
    private void initBiometricP() {
        BiometricPrompt mBiometricPrompt = new BiometricPrompt.Builder(getContext())
                .setTitle("指纹验证")
                .setDescription("描述")
                .setNegativeButton("手势认证",
                        getActivity().getMainExecutor(),
                        (dialogInterface, i) -> {
                            mCancellationSignal.cancel();
                            Log.i("ttt", "取消");
                        })
                .build();

        Signature mSignature;
        try {
            KeyPair keyPair = generateKeyPair(DEFAULT_KEY_NAME, true);
            // 将密钥对的公钥部分发送到服务器，该公钥将用于认证
            String mToBeSignedMessage = new StringBuilder()
                    .append(Base64.encodeToString(keyPair.getPublic().getEncoded(), Base64.URL_SAFE))
                    .append(":")
                    .append(DEFAULT_KEY_NAME)
                    .append(":")
                    // Generated by the server to protect against replay attack
                    .append("12345")
                    .toString();

            mSignature = initSignature(DEFAULT_KEY_NAME);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        BiometricPrompt.AuthenticationCallback mAuthenticationCallback = new BiometricPrompt.AuthenticationCallback() {
            @Override
            public void onAuthenticationError(int errorCode, CharSequence errString) {
                super.onAuthenticationError(errorCode, errString);
                mCancellationSignal.cancel();
            }

            @Override
            public void onAuthenticationSucceeded(BiometricPrompt.AuthenticationResult result) {
                super.onAuthenticationSucceeded(result);
                success();
            }

            @Override
            public void onAuthenticationFailed() {
                super.onAuthenticationFailed();
                AlertUtil.t(getContext(), "认证失败", Toast.LENGTH_SHORT);
                mCancellationSignal.cancel();
            }
        };
        if (mCancellationSignal == null) {
            mCancellationSignal = new CancellationSignal();
        }
        if (mSignature != null) {
            mBiometricPrompt.authenticate(new BiometricPrompt.CryptoObject(mSignature), mCancellationSignal, getActivity().getMainExecutor(), mAuthenticationCallback);
        }
    }

    /**
     * Generate NIST P-256 EC Key pair for signing and verification
     *
     * @param keyName
     * @param invalidatedByBiometricEnrollment
     * @return
     */
    @TargetApi(Build.VERSION_CODES.P)
    private KeyPair generateKeyPair(String keyName, boolean invalidatedByBiometricEnrollment) throws Exception {
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore");

        KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder(keyName,
                KeyProperties.PURPOSE_SIGN)
                .setAlgorithmParameterSpec(new ECGenParameterSpec("secp256r1"))
                .setDigests(KeyProperties.DIGEST_SHA256,
                        KeyProperties.DIGEST_SHA384,
                        KeyProperties.DIGEST_SHA512)
                // Require the user to authenticate with a biometric to authorize every use of the key
                .setUserAuthenticationRequired(true)
                .setInvalidatedByBiometricEnrollment(invalidatedByBiometricEnrollment);

        keyPairGenerator.initialize(builder.build());

        return keyPairGenerator.generateKeyPair();
    }

    @Nullable
    private KeyPair getKeyPair(String keyName) throws Exception {
        KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
        keyStore.load(null);
        if (keyStore.containsAlias(keyName)) {
            // Get public key
            PublicKey publicKey = keyStore.getCertificate(keyName).getPublicKey();
            // Get private key
            PrivateKey privateKey = (PrivateKey) keyStore.getKey(keyName, null);
            // Return a key pair
            return new KeyPair(publicKey, privateKey);
        }
        return null;
    }

    @Nullable
    private Signature initSignature(String keyName) throws Exception {
        KeyPair keyPair = getKeyPair(keyName);

        if (keyPair != null) {
            Signature signature = Signature.getInstance("SHA256withECDSA");
            signature.initSign(keyPair.getPrivate());
            return signature;
        }
        return null;
    }

    /**
     * 创建密钥
     */
    @TargetApi(Build.VERSION_CODES.M)
    private void initKey() {
        try {
            keyStore = KeyStore.getInstance("AndroidKeyStore");
            keyStore.load(null);
            KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
            KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder(DEFAULT_KEY_NAME,
                    KeyProperties.PURPOSE_ENCRYPT |
                            KeyProperties.PURPOSE_DECRYPT)
                    .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
                    .setUserAuthenticationRequired(true)
                    .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7);
            keyGenerator.init(builder.build());
            keyGenerator.generateKey();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 初始化Cipher(加密类，指纹扫描器会使用这个对象来判断认证结果的合法性)
     */
    @TargetApi(Build.VERSION_CODES.M)
    private Cipher initCipher() {
        try {
            SecretKey key = (SecretKey) keyStore.getKey(DEFAULT_KEY_NAME, null);
            Cipher cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
                    + KeyProperties.BLOCK_MODE_CBC + "/"
                    + KeyProperties.ENCRYPTION_PADDING_PKCS7);
            cipher.init(Cipher.ENCRYPT_MODE, key);
            return cipher;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void success() {
        AlertUtil.t(getContext(), "认证成功", Toast.LENGTH_SHORT);
        dismissAllowingStateLoss();
    }

    @Override
    public void fail() {
        AlertUtil.t(getContext(), "认证失败", Toast.LENGTH_SHORT);
    }

    @Override
    public Cipher getCipher() {
        return cipher;
    }
}
